home *** CD-ROM | disk | FTP | other *** search
/ SGI Hot Mix 17 / Hot Mix 17.iso / HM17_SGI / research / lib / read_ascii.pro < prev    next >
Text File  |  1997-07-08  |  27KB  |  879 lines

  1. ; $Id: read_ascii.pro,v 1.8 1997/04/08 17:36:58 stevek Exp $
  2. ;
  3. ; Copyright (c) 1996-1997, Research Systems, Inc.  All rights reserved.
  4. ;       Unauthorized reproduction prohibited.
  5. ;+
  6. ; NAME:
  7. ;    READ_ASCII
  8. ;
  9. ; PURPOSE:
  10. ;    Read data from an ASCII file into IDL.
  11. ;
  12. ; CATEGORY:
  13. ;    Input/Output.
  14. ;
  15. ; CALLING SEQUENCE:
  16. ;    data = READ_ASCII(file)
  17. ;
  18. ; INPUTS:
  19. ;    file          - Name of file to read.
  20. ;
  21. ; INPUT KEYWORD PARAMETERS:
  22. ;    record_start      - 1st sequential "record" (see DESCRIPTION) to read.
  23. ;                Default = 0 (the first record of the file).
  24. ;    num_records      - Number of records to read.
  25. ;                Default = Read up to and including the last record.
  26. ;
  27. ;    template      - ASCII file template (e.g., generated by function
  28. ;                ASCII_TEMPLATE) describing attributes of the file
  29. ;                to read.  Specific attributes contained in the
  30. ;                template may be overridden by keywords below.
  31. ;                Default = (see the keywords below).
  32. ;
  33. ;    data_start      - Number of lines of header to skip.
  34. ;                Default (if no template) = 0L.
  35. ;    delimiter      - Character that delimits fields.
  36. ;                Default (if no template) = '' = use fields(*).loc.
  37. ;    missing_value      - Value to replace any missing/invalid data.
  38. ;                Default (if no template) = !VALUES.F_NAN.
  39. ;    comment_symbol      - String identifying comments
  40. ;                (from comment_symbol to the next end-of-line).
  41. ;                Default (if no template) = '' = no comments.
  42. ;    fields          - Descriptions of the data fields, formatted as
  43. ;                           an array of structures containing the tags:
  44. ;                              name  = name of the field (string)
  45. ;                              type  = type of field as returned by SIZE (long)
  46. ;                              loc   = offset from the beginning of line to
  47. ;                                      the start of the field (long)
  48. ;                              group = sequential group the field is in (int)
  49. ;                Default (if no template) = 
  50. ;                              {name:'field', type:4L, loc:0L, group:0}.
  51. ;
  52. ;    verbose          - If set, print runtime messages.
  53. ;                Default = Do not print them.
  54. ;
  55. ; OUTPUT KEYWORD PARAMETERS:
  56. ;    header            - The header read (string array of length
  57. ;                data_start).  If no header, empty string returned.
  58. ;
  59. ;    count          - The number of records read.
  60. ;
  61. ; OUTPUTS:
  62. ;    The function returns an anonymous structure, where each field in
  63. ;    the structure is a "field" of the data read (see DESCRIPTION).
  64. ;    If no records are read, 0 is returned.
  65. ;
  66. ; COMMON BLOCKS:
  67. ;    None.
  68. ;
  69. ; SIDE EFFECTS:
  70. ;    None.
  71. ;
  72. ; RESTRICTIONS:
  73. ;    See DESCRIPTION.
  74. ;
  75. ; DESCRIPTION:
  76. ;    ASCII files handled by this routine consist of an optional header
  77. ;    of a fixed number of lines, followed by columnar data.  Files may
  78. ;    also contain comments, which exist between a user-specified comment
  79. ;    string and the corresponding end-of-line.
  80. ;
  81. ;    One or more rows of data constitute a "record."  Each data element
  82. ;    within a record is considered to be in a different column, or "field."
  83. ;    Adjacent fields may be "grouped" into multi-column fields.
  84. ;    The data in one field must be of, or promotable to, a single
  85. ;    type (e.g., FLOAT).
  86. ;
  87. ; EXAMPLES:
  88. ;    ; Using default file attributes.
  89. ;    data = READ_ASCII(file)
  90. ;
  91. ;    ; Setting specific file attributes.
  92. ;    data = READ_ASCII(file, DATA_START=10)
  93. ;
  94. ;    ; Using a template to define file attributes.
  95. ;    data = READ_ASCII(file, TEMPLATE=template)
  96. ;
  97. ;    ; Using a template to define file attributes,
  98. ;    ; and overriding some of those attributes.
  99. ;    data = READ_ASCII(file, TEMPLATE=template, DATA_START=10)
  100. ;
  101. ;    ; Using the ASCII_TEMPLATE GUI to generate a template in place.
  102. ;    data = READ_ASCII(file, TEMPLATE=ASCII_TEMPLATE(file))
  103. ;
  104. ;    ; An example defining fields by hand.
  105. ;    fields = REPLICATE({name:'', type:0L, loc:0L, group:0}, 2, 3)
  106. ;    num = N_ELEMENTS(fields)
  107. ;    fields(*).name  = 'field' + STRTRIM(STRING(INDGEN(num) + 1), 2)
  108. ;    fields(*).type  = REPLICATE(4L, num)
  109. ;    fields(*).loc   = [0L,10L, 0L,15L, 0L,12L]
  110. ;    fields(*).group = INDGEN(num)
  111. ;    data = READ_ASCII(file, FIELDS=fields)
  112. ;
  113. ;    ; Another example defining fields by hand.
  114. ;    void = {sMyStructName, name:'', type:0L, loc:0L, group:0}
  115. ;    fields = [ [ {sMyStructName, 'frog', (SIZE(''))(1),  0L, 0},   $
  116. ;                 {sMyStructName, 'bird', (SIZE(0 ))(1), 15L, 1} ], $
  117. ;               [ {sMyStructName, 'fish', (SIZE(0.))(1),  0L, 2},   $
  118. ;                 {sMyStructName, 'bear', (SIZE(0D))(1), 15L, 3} ], $
  119. ;               [ {sMyStructName, 'boar', (SIZE(0B))(1),  0L, 4},   $
  120. ;                 {sMyStructName, 'nerd', (SIZE(OL))(1), 15L, 5} ]  ]
  121. ;    data = READ_ASCII(file, FIELDS=fields)
  122. ;
  123. ; DEVELOPMENT NOTES:
  124. ;
  125. ;    - see ???,!!!,xxx in the code
  126. ;
  127. ;    - error check input 'delimiter' to be a string (not a byte)
  128. ;
  129. ;    - finish implementing new 'fields' input kw
  130. ;      (old approach had separate kw for each attribute of a field)
  131. ;
  132. ; MODIFICATION HISTORY:
  133. ;    AL & RPM, 8/96 - Written.
  134. ;
  135. ;-
  136. ;
  137. ; ROUTINES:
  138. ;    fun ra_parse_value    - parse a value
  139. ;    pro ra_resize_pointers    - increase the size of the data vectors
  140. ;    pro ra_parse_d_values    - parse delimited values
  141. ;    pro ra_get_next_record    - read in the next record
  142. ;    fun ra_read_from_templ    - read a file given an ASCII file template
  143. ;    fun ra_valid_template    - check if a template is valid
  144. ;    fun ra_stringit        - convert to string and remove white space
  145. ;       fun ra_check_file       - check validity of input file
  146. ;    fun read_ascii        - the main routine
  147.  
  148. ; -----------------------------------------------------------------------------
  149. ;
  150. ;  Purpose:  Given a start and end location into a string, parse out the
  151. ;         appropriate numerical or string value. In the case of a blank
  152. ;         string or any error, substitute in the missing value.
  153. ;
  154. function ra_parse_value, line, type, sptr, len, $
  155.   nlut=nlut, missing_value=missing_value
  156.  
  157.   ;  Grab the sub string and remove leading/trailing white space.
  158.   ;
  159.   sub_str = strtrim(strmid(line, sptr, len), 2)
  160.  
  161.   if (type eq 7) then $                    ; string
  162.     return, sub_str $
  163.  
  164.   else begin
  165.  
  166.     ;  If the string is empty, return missing value.
  167.     ;
  168.     ;  Else if the string doesn't start as a proper number
  169.     ;  (i.e., with 0-9,+,-,.), return missing value.
  170.     ;
  171.     if ((sub_str eq '') or $
  172.         ((nlut(byte(strmid(sub_str,0,1))))(0) eq 0)) then $
  173.       return, missing_value                ; missing
  174.  
  175.     case type of
  176.       0: return, 0B                        ; ??? undefined -> 0B ???
  177.       1: return, byte(fix(sub_str))        ; byte
  178.       2: return, fix(sub_str)              ; int
  179.       3: return, long(sub_str)             ; long
  180.       4: return, float(sub_str)            ; float
  181.       5: return, double(sub_str)           ; double
  182.       6: return, complex(sub_str)          ; complex
  183.     endcase
  184.   endelse
  185.  
  186. end            ; ra_parse_value
  187.  
  188. ; -----------------------------------------------------------------------------
  189. ;
  190. ;  Purpose:  Go through the array of pointers to arrays and increase the
  191. ;         size of each array by blk_size elements.
  192. ;
  193. pro ra_resize_pointers, p_vals, blk_size
  194.  
  195.   for i=0, n_elements(p_vals)-1 do begin 
  196.     if (ptr_valid(p_vals(i))) then begin
  197.       info = size(*p_vals(i))
  198.       *p_vals(i) = [*p_vals(i), make_array(blk_size, type=info(info(0)+1))]
  199.     endif
  200.   endfor
  201.  
  202. end            ; ra_resize_pointers
  203.  
  204. ; -----------------------------------------------------------------------------
  205. ;
  206. ;  Purpose:  Parse out values from a line of text which are separated by
  207. ;         a given delimiter.
  208. ;
  209. pro ra_parse_d_values, line, types, p_vals, floc, rec_count, $
  210.   delimit, nlut, missing_value
  211.  
  212.   ;  If delimiter is space, compress and remove leading/trailing spaces.
  213.   ;
  214.   if (delimit eq 32B) then line = strtrim(strcompress(line), 2)
  215.   bline = byte(line)
  216.  
  217.   ;  Find where the delimiters are.
  218.   ;
  219.   ptr = where(bline eq delimit, bcount)
  220.   if (bcount eq 0) then begin
  221.     ptr = [-1, n_elements(bline)+1]
  222.     bcount = 0
  223.   endif else $
  224.     ptr = [-1, ptr, n_elements(bline)+1]
  225.  
  226.   ;  Loop for each field of the line.
  227.   ;
  228.   for i=0, bcount<(n_elements(types)-floc-1) do $
  229.  
  230.     ;  Parse the field if not skipping it.
  231.     ;
  232.     if (types(floc+i) gt 0) then begin
  233.       (*p_vals(floc+i))(rec_count) = $
  234.         ra_parse_value(line, types(floc+i), ptr(i)+1, ptr(i+1)-ptr(i)-1, $
  235.           nlut=nlut, missing_value=missing_value)
  236.     endif
  237.  
  238. end            ; ra_parse_d_values
  239.  
  240. ; -----------------------------------------------------------------------------
  241. ;
  242. ;  Purpose:  Read in the next n lines of text (skipping blank lines and
  243. ;         commented lines signified by template.commentSymbol at start;
  244. ;         also throw away comment portions of regular lines).
  245. ;
  246. pro ra_get_next_record, template, unit, lines, end_reached=end_reached
  247.   ;
  248.   catch, error_status
  249.   if (error_status ne 0) then begin
  250.     end_reached = 1B
  251.     return
  252.   endif
  253.   ;
  254.   line = ''
  255.   count = 0
  256.   end_reached = 0B
  257.  
  258.   ;  Checking for comments...
  259.   ;
  260.   if (template.commentSymbol ne '') then begin
  261.     while (count lt n_elements(lines)) do begin
  262.       readf, unit, line
  263.       pos = strpos(line, template.commentSymbol, 0)
  264.       if (strtrim(line,2) ne '' and pos(0) ne 0) then begin
  265.         if (pos(0) eq -1) then lines(count) = line $
  266.         else                   lines(count) = strmid(line,0,pos(0)-1)
  267.         count = count + 1
  268.       endif
  269.     endwhile
  270.  
  271.   ;  NOT checking for comments...
  272.   ;
  273.   endif else begin
  274.     while (count lt n_elements(lines)) do begin
  275.       readf, unit, line
  276.       if (strtrim(line,2) ne '') then begin
  277.         lines(count) = line
  278.         count = count + 1
  279.       endif
  280.     endwhile
  281.   endelse
  282.  
  283. end            ; ra_get_next_record
  284.  
  285. ; -----------------------------------------------------------------------------
  286. ;
  287. ;  Purpose:  Given a template structure, open an ASCII file and parse out the
  288. ;         numerical and string values based upon the parameters of the
  289. ;         given template.
  290. ;
  291. ;        (a) white space separates fields lined up in columns
  292. ;        (b) a delimiter character separates fields
  293. ;
  294. ;     Note:  When skipping to the start of the data, blank lines ARE included
  295. ;         as lines to skip, but once you get to the data, subsequent blank
  296. ;         lines (as well as comment lines) are ignored.
  297. ;
  298. ;         Function returns an array of pointers to the data read;
  299. ;         if no data read, 0 is returned.
  300. ;
  301. function ra_read_from_templ, $
  302.     name, $        ; IN: name of ASCII file to read
  303.     template, $        ; IN: ASCII file template
  304.     start_record, $    ; IN: first record to read
  305.     records_to_read, $    ; IN: number of records to read
  306.     doVerbose, $    ; IN: 1B = print runtime messages
  307.     num_fields_read, $    ; OUT: number of fields successfully read
  308.     fieldNames, $       ; OUT: associated name of each field read
  309.     rec_count, $    ; OUT: number of records successfully read
  310.     header=header    ; OUT: (opt) header read
  311.  
  312.   widget_control, /hourglass
  313.  
  314.   ;  Set default numbers.
  315.   ;
  316.   num_fields_read = 0
  317.   rec_count = 0l
  318.  
  319.   ;  Catch errors.
  320.   ;
  321.   catch, error_status
  322.   if (error_status ne 0) then begin
  323.     print,'Unexpected Error: ' + !err_string
  324.     rec_count = 0l
  325.     return, 0
  326.   endif
  327.  
  328.   ;  Open the file.
  329.   ;
  330.   openr, unit, name, /get_lun
  331.  
  332.   ;  Create character (byte) lookup table.
  333.   ;
  334.   nlut = bytarr(256)
  335.   nlut(48:57) = 1       ; 0 thru 9
  336.   nlut(byte('-')) = 1   ; -
  337.   nlut(byte('+')) = 1   ; +
  338.   nlut(byte('.')) = 1   ; .
  339.  
  340.   ;  Set various parameters.
  341.   ;
  342.   blk_size = 1000
  343.   lines_per_record = n_elements(template.fieldCount)
  344.   num_fields = template.fieldCount
  345.   tot_num_fields = total(template.fieldCount)
  346.   types = template.fieldTypes
  347.   locs = template.fieldLocations
  348.  
  349.   ;  Define an array of variables for each field.
  350.   ;
  351.   p_vals = ptrarr(tot_num_fields)
  352.   for i=0, tot_num_fields-1 do $
  353.     if (types(i) gt 0) then $
  354.       p_vals(i) = ptr_new(make_array(blk_size, type=types(i)))
  355.  
  356.   ;  Read the header and skip to the start of the data.
  357.   ;
  358.   dataStart = template.dataStart
  359.   if (dataStart gt 0) then begin
  360.     if (doVerbose) then $
  361.       print, 'Reading header of ' + strtrim(string(dataStart), 2) + $
  362.         ' lines ...', format='(A/)'
  363.     header = strarr(dataStart)
  364.     readf, unit, header
  365.   endif else $
  366.     header = ''
  367.  
  368.   ;  Skip to the start of requested data.
  369.   ;
  370.   lines = strarr(lines_per_record)
  371.   if ((doVerbose) and (start_record gt 0)) then $
  372.     print, 'Skipping ' + strtrim(string(start_record), 2) + ' records ...', $
  373.       format='(A/)'
  374.   for i = 0L, start_record-1 do $
  375.     ra_get_next_record, template, unit, lines
  376.  
  377.   end_reached = 0B
  378.  
  379.   ; ------------------------------------
  380.   ; nice columned data...
  381.   ; ------------------------------------
  382.   ;
  383.   if (template.delimiter eq 0B) then begin
  384.     while (((rec_count lt records_to_read) or (records_to_read eq 0)) and $
  385.            (not end_reached)) do begin
  386.  
  387.       ;  Read the next record.
  388.       ;
  389.       ra_get_next_record, template, unit, lines, end_reached=end_reached
  390.       if (not end_reached) then begin
  391.         floc = 0
  392.  
  393. ;xxx
  394.       if (doVerbose) then $
  395.         print, 'Processing sequential record ' + $
  396.           strtrim(string(rec_count+1), 2) + ' ...'
  397.  
  398.         ;  Loop for each line in a record, parsing values.
  399.         ;
  400.         for j=0, lines_per_record-1 do begin   
  401.           for k=0, num_fields(j)-1 do begin
  402.             if (types(floc+k) gt 0) then begin
  403.               if (k eq num_fields(j)-1) then $
  404.                 len = strlen(lines(j)) - locs(floc+k) $
  405.               else $
  406.                 len = locs(floc+k+1) - locs(floc+k)
  407.               (*p_vals(floc+k))(rec_count) = ra_parse_value(lines(j), $
  408.                 types(floc+k), locs(floc+k), len, $
  409.                 nlut=nlut, missing=template.missingValue)
  410.             endif
  411.           endfor
  412.           floc = floc + num_fields(j)
  413.         endfor
  414.  
  415.         rec_count = rec_count + 1l
  416.         if (rec_count mod blk_size eq 0) then $
  417.           ra_resize_pointers, p_vals, blk_size
  418.       endif
  419.     endwhile
  420.  
  421.   ; ------------------------------------
  422.   ; data separated by a delimiter...
  423.   ; ------------------------------------
  424.   ;
  425.   endif else begin
  426.     while (((rec_count lt records_to_read) or (records_to_read eq 0)) and $
  427.            (not end_reached)) do begin
  428.  
  429.       ;  Read the next record.
  430.       ;
  431.       ra_get_next_record, template, unit, lines, end_reached=end_reached
  432.       if (not end_reached) then begin
  433.         floc = 0
  434.  
  435. ;xxx
  436.       if (doVerbose) then $
  437.         print, 'Processing sequential record ' + $
  438.           strtrim(string(rec_count+1), 2) + ' ...'
  439.  
  440.         ;  Loop for each line in a record, parsing values.
  441.         ;
  442.         for i=0, lines_per_record-1 do begin
  443.           ra_parse_d_values, lines(i), types, p_vals, floc, $
  444.             rec_count, template.delimiter, nlut, template.missingValue
  445.           floc = long(total(num_fields(0:i)))
  446.         endfor
  447.  
  448.         rec_count = rec_count + 1l
  449.         if (rec_count mod blk_size eq 0) then $
  450.           ra_resize_pointers, p_vals, blk_size
  451.       endif
  452.     endwhile
  453.   endelse
  454.   ; ------------------------------------
  455.  
  456.   free_lun, unit
  457.  
  458.   if (doVerbose) then $
  459.     print, 'Total records read:  ' + strtrim(string(rec_count), 2), $
  460.       format='(A/)'
  461.  
  462.   ;  If records were read ...
  463.   ;
  464.   if (rec_count gt 0) then begin
  465.  
  466.     ;  Set the output arrays to exactly the correct size.
  467.     ;
  468.     for i=0, tot_num_fields-1 do $
  469.       if (p_vals(i) ne ptr_new()) then $
  470.         *p_vals(i) = (*p_vals(i))(0:rec_count-1)
  471.  
  472.     ;  Check the groups array and arrange the output pointers into
  473.     ;  (potentially) groups of 2-D arrays.
  474.     ;
  475.     groups = template.fieldGroups
  476.  
  477.     ;  Don't include any groups which are skipped fields.
  478.     ;
  479.     ptr = where(types eq 0, numSkip)
  480.     for i=0, numSkip-1 do groups(ptr(i)) = max(groups) + 1
  481.  
  482.     ;
  483.     ;  Concatenate 1-D arrays into multi arrays based upon groupings.
  484.     ;
  485.     uptr = uniq(groups, sort(groups))
  486.     if (n_elements(uptr) lt n_elements(groups)) then begin
  487.       for i=0, n_elements(uptr)-1 do begin
  488.         lptr = where(groups eq groups(uptr(i)), lcount)
  489.         if (lcount gt 1) then begin
  490.           p_new = p_vals(lptr(0))
  491.           for j=1,lcount-1 do begin
  492.             *p_new = [[*p_new],[*p_vals(lptr(j))]]
  493.             ptr_free, p_vals(lptr(j))
  494.             p_vals(lptr(j)) = ptr_new()
  495.           endfor
  496.           *p_new = transpose(*p_new)
  497.         endif
  498.       endfor
  499.     endif
  500.  
  501.     ;  Return the pointers that contain data, if any.
  502.     ;  and the associated fieldNames for these pointers
  503.     ;
  504.     ptr = where(p_vals ne ptr_new(), num_fields_read)
  505.  
  506.     if (num_fields_read gt 0) then begin ; data successfully read
  507.       fieldNames = template.fieldNames(ptr)
  508.       return, p_vals(ptr)
  509.     endif else begin                           ; no data read
  510.       rec_count = 0l
  511.       return, 0
  512.     endelse
  513.  
  514.   endif else $                           ; no data read
  515.     return, 0
  516.  
  517. end            ; ra_read_from_templ
  518.  
  519. ; -----------------------------------------------------------------------------
  520. ;
  521. ;  Purpose:  Return 1B if the template is valid, else 0B.
  522. ;
  523. function ra_valid_template, $
  524.   template, $       ; IN: template to check
  525.   message           ; OUT: error message if the template is not valid
  526.  
  527.   message = ''
  528.  
  529.   ;  Make sure it's a structure.
  530.   ;
  531.   sz = size(template)
  532.   if (sz(sz(0)+1) ne 8) then begin
  533.     message = 'Template is not a structure.'
  534.     RETURN, 0B    
  535.   endif
  536.  
  537.   ;  Get tag names and make sure version field is present.
  538.   ;
  539.   tagNamesFound = TAG_NAMES(template)
  540.   void = WHERE(tagNamesFound eq 'VERSION', count)
  541.   if (count ne 1) then begin
  542.     message = 'Version field missing from template.'
  543.     RETURN, 0B    
  544.   endif
  545.  
  546.   ;  Do checking based on version.
  547.   ;
  548.   case (template.version) of
  549.  
  550.     1.0: begin
  551.  
  552.       ;  Set the names of the required tags (version alread checked).
  553.       ;
  554.       tagNamesRequired = STRUPCASE([ $
  555.         'dataStart', 'delimiter', 'missingValue', 'commentSymbol', $
  556.         'fieldCount', 'fieldTypes', 'fieldNames', 'fieldLocations', $
  557.         'fieldGroups'])
  558.  
  559.       ;  Check that all of the required tags are present.
  560.       ;
  561.       for seqTag = 0, N_ELEMENTS(tagNamesRequired)-1 do begin
  562.         tag = tagNamesRequired(seqTag)
  563.         void = WHERE(tagNamesFound eq tag, count)
  564.         if (count ne 1) then begin
  565.           message = tag + ' field missing from template.'
  566.           RETURN, 0B    
  567.         endif
  568.       endfor
  569.  
  570.     end
  571.  
  572.     else: begin
  573.       message = 'The only recognized template version is 1.0 (float).'
  574.       RETURN, 0B    
  575.     end
  576.   endcase
  577.  
  578.   ;  Return that the template is valid.
  579.   ;
  580.   RETURN, 1B
  581.  
  582. end            ; ra_valid_template
  583.  
  584. ; -----------------------------------------------------------------------------
  585. ;
  586. ;  Purpose:  Convert to string and remove extra white space.
  587. ;
  588. function ra_stringit, value
  589.  
  590.   result = STRTRIM( STRCOMPRESS( STRING(value) ), 2 )
  591.  
  592.   num = N_ELEMENTS(result)
  593.  
  594.   if (num le 1) then RETURN, result
  595.  
  596.   ;  If two or more values, concatenate them.
  597.   ;
  598.   delim = ' '
  599.   ret = result(0)
  600.   for i = 1, num-1 do $
  601.     ret = ret + delim + result(i)
  602.  
  603.   RETURN, ret
  604.  
  605. end            ; ra_stringit
  606.  
  607. ; -----------------------------------------------------------------------------
  608. ;
  609. ;  Purpose: Check that the input filename is a string, exists, and appears
  610. ;           to be ASCII... also if the file is just columned ascii data, 
  611. ;           then guess at the number of default columns of data.
  612. ;
  613. function ra_check_file, fname, default_num_columns=default_num_columns
  614.   catch, error_status
  615.   if (error_status ne 0) then begin
  616.     if (n_elements(unit) gt 0) then free_lun, unit
  617.     return, -3 ; unexpected error reading from file
  618.   endif
  619.   ;
  620.   info = size(fname)
  621.   if (info(info(0)+1) ne 7) then return, -1 ; filename isn't a string
  622.   ;
  623.   openr, unit, fname, error=error, /get_lun
  624.   if (error eq 0) then begin
  625.     finfo = fstat(unit)
  626.     ; set non-ascii values in lookup table
  627.     ;
  628.     lut = bytarr(256) + 1b
  629.     lut[7:13]   = 0b
  630.     lut[32:127] = 0b
  631.     data = bytarr(32767<finfo.size, /nozero)
  632.     readu, unit, data
  633.     carriage_return = (total(data eq 10b) gt 0 or total(data eq 13b) gt 0)
  634.     if (carriage_return eq 0) then begin
  635.     ; looks like a binary file
  636.     ;
  637.       free_lun, unit
  638.       return, -4
  639.     endif
  640.     non_printable   = (total(lut(data)) gt 0)
  641.     if (non_printable) then begin
  642.     ; looks like a binary file
  643.     ;
  644.       free_lun, unit
  645.       return, -4
  646.     endif
  647.     ; everything looks ok, now guess at the number of columns...
  648.     ;
  649.     point_lun, unit, 0
  650.     line = ''
  651.     readf, unit, line
  652.     free_lun, unit
  653.     bline = byte(strtrim(strcompress(line),2))
  654.     ptr = where(bline eq 32, num_spaces)
  655.     default_num_columns = num_spaces + 1
  656.   endif else $
  657.     return, -2 ; unable to open file
  658. end
  659.  
  660. ; -----------------------------------------------------------------------------
  661. ;
  662. ;  Purpose:  The main routine.
  663. ;
  664. function read_ascii, $
  665.     file, $                ; IN:
  666.     RECORD_START=recordStart, $        ; IN: (opt)
  667.     NUM_RECORDS=numRecords, $        ; IN: (opt)
  668.     TEMPLATE=template, $        ; IN: (opt)
  669.     DATA_START=dataStart, $        ; IN: (opt)
  670.     DELIMITER=delimiter, $        ; IN: (opt)
  671.     MISSING_VALUE=missingValue, $    ; IN: (opt)
  672.     COMMENT_SYMBOL=commentSymbol, $    ; IN: (opt)
  673.     FIELDS=fields, $            ; IN: (opt)
  674.     VERBOSE=verbose, $            ; IN: (opt)
  675.     HEADER=header, $            ; OUT: (opt)
  676.     COUNT=count                ; OUT: (opt)
  677.  
  678. ;xxx
  679. ;later add a VERSION kw ?
  680.  
  681.   ;  Set to return to caller on error.
  682.   ;
  683.   ON_ERROR, 2
  684. ;  ON_ERROR, 0
  685.  
  686.   ;  Set some defaults.
  687.   ;
  688.   count = 0
  689.   currentVersion = 1.0
  690.   doVerbose = KEYWORD_SET(verbose)
  691.  
  692.   ; If no file specified, use DIALOG_PICKFILE
  693.   ;
  694.   if (n_elements(file) eq 0) then begin
  695.     file = DIALOG_PICKFILE(/MUST_EXIST)
  696.     if (file eq '') then RETURN, 0
  697.   endif
  698.  
  699.   ;
  700.   ; check that the file is readable and appears to be ASCII
  701.   ret = ra_check_file(file, default_num_columns=default_num_columns)
  702.   case ret of
  703.     -1: MESSAGE, 'File name must be a string.'
  704.     -2: MESSAGE, 'File "' + file + '" not found.'
  705.     -3: MESSAGE, 'Error Reading from file "' + file + '"
  706.     -4: MESSAGE, 'File "' + file + '" is not an ASCII file.'
  707.     else:
  708.   endcase
  709.  
  710.   ;  Set which records to read.
  711.   ;
  712.   if (N_ELEMENTS(recordStart) ne 0) then recordStartUse = recordStart $
  713.                                     else recordStartUse = 0
  714.   if (N_ELEMENTS(numRecords) ne 0)  then numRecordsUse = numRecords $
  715.                                     else numRecordsUse = 0
  716.  
  717.   ; ---------------------------------------
  718.   ;  Set default file attributes.
  719.   ; ---------------------------------------
  720.  
  721.   ;  If a template was input, then use those attributes.
  722.   ;
  723.   if (N_ELEMENTS(template) gt 0) then begin
  724.  
  725.     ;  Make sure the template is valid.
  726.     ;
  727.     if (not ra_valid_template(template, message)) then $
  728.       MESSAGE, message
  729.  
  730.     ;  Use the template's attributes as default.
  731.     ;
  732.     versionUse        = template.version
  733.     dataStartUse    = template.dataStart
  734.     delimiterUse    = STRING(template.delimiter)
  735.     missingValueUse    = template.missingValue
  736.     commentSymbolUse    = template.commentSymbol
  737.     fieldCountUse    = template.fieldCount
  738.     fieldTypesUse    = template.fieldTypes
  739.     fieldNamesUse    = template.fieldNames
  740.     fieldLocationsUse    = template.fieldLocations
  741.     fieldGroupsUse    = template.fieldGroups
  742.  
  743.     ;  Get the final number of fields.
  744.     ;
  745.     if (N_ELEMENTS(fieldCount) ne 0) then $
  746.       fieldCountUse = fieldCount
  747.     numFields = TOTAL(fieldCountUse)
  748.  
  749.   ;  No template input, so set defaults by hand.
  750.   ;
  751.   endif else begin
  752.  
  753.     ;  Set scalar defaults.
  754.     ;
  755.     versionUse        = currentVersion
  756.     dataStartUse    = 0L
  757.     delimiterUse    = ' '
  758.     missingValueUse    = 0.0
  759.     commentSymbolUse    = ''
  760.  
  761.     ; use best guess of default number of fields without any template
  762.     ; information to go on...
  763.     numFields = (fieldCountUse = default_num_columns)
  764.     ;  Set vector defaults.
  765.     ;
  766.     fieldTypesUse    = REPLICATE(4L, numFields)
  767.     digits_str = strtrim(string(strlen(strtrim(string(numFields),2))),2)
  768.     fstr = '(i' + digits_str + '.' + digits_str + ')'
  769.     fieldNamesUse    = 'field' + STRING(INDGEN(numFields)+1, format=fstr)
  770.     fieldLocationsUse    = LONARR(numFields)
  771.     fieldGroupsUse    = INTARR(numFields)
  772.  
  773.   endelse
  774.  
  775.   ; ---------------------------------------
  776.   ;  Optionally override the attributes.
  777.   ; ---------------------------------------
  778.  
  779.   if (N_ELEMENTS(dataStart) ne 0) then $
  780.     dataStartUse = dataStart
  781.   if (N_ELEMENTS(delimiter) ne 0) then $
  782.     delimiterUse = delimiter
  783.   if (N_ELEMENTS(missingValue) ne 0) then $
  784.     missingValueUse = missingValue
  785.   if (N_ELEMENTS(commentSymbol) ne 0) then $
  786.     commentSymbolUse = commentSymbol
  787.  
  788.   if (N_ELEMENTS(fieldTypes) ne 0) then $
  789.     fieldTypesUse = fieldTypes
  790.   if (N_ELEMENTS(fieldNames) ne 0) then $
  791.     fieldNamesUse = fieldNames
  792.   if (N_ELEMENTS(fieldLocations) ne 0) then $
  793.     fieldLocationsUse = fieldLocations
  794.   if (N_ELEMENTS(fieldGroups) ne 0) then $
  795.     fieldGroupsUse = fieldGroups
  796.  
  797.   ; ---------------------------------------
  798.  
  799.   ;  Error check the field data.
  800.   ;
  801.   lengths = [ $
  802.     N_ELEMENTS(fieldTypesUse), $
  803.     N_ELEMENTS(fieldNamesUse), $
  804.     N_ELEMENTS(fieldLocationsUse), $
  805.     N_ELEMENTS(fieldGroupsUse) $
  806.     ]
  807.   if (TOTAL(ABS(lengths - SHIFT(lengths, 1))) ne 0) then $
  808.     MESSAGE, 'Field data (types/names/locs/groups) not the same length.'
  809.  
  810.   ;  Set the template to use.
  811.   ;
  812.   templateUse = { $
  813.     version: versionUse, $
  814.     dataStart: dataStartUse, $
  815.     delimiter: BYTE(delimiterUse), $
  816.     missingValue: missingValueUse, $
  817.     commentSymbol: commentSymbolUse, $
  818.     fieldCount: fieldCountUse, $
  819.     fieldTypes: fieldTypesUse, $
  820.     fieldNames: fieldNamesUse, $
  821.     fieldLocations: fieldLocationsUse, $
  822.     fieldGroups: fieldGroupsUse $
  823.     }
  824.  
  825.   ;  Print verbose information.
  826.   ;
  827.   if (doVerbose) then begin
  828.     PRINT, 'Using the following file attributes ...', FORMAT='(/A)'
  829.     PRINT, '        Data Start:  ' + STRTRIM(STRING(dataStartUse), 2)
  830.     PRINT, '         Delimiter:  ' + $
  831.                              STRTRIM(STRING(FIX(BYTE(delimiterUse))), 2) + 'B'
  832.     PRINT, '     Missing Value:  ' + STRTRIM(STRING(missingValueUse), 2)
  833.     PRINT, '    Comment Symbol:  ' + commentSymbolUse
  834.     PRINT, '      Field Counts:  ' + ra_stringit(fieldCountUse)
  835.     PRINT, '      Field Types :  ' + ra_stringit(fieldTypesUse)
  836.     PRINT, '      Field Names :  ' + ra_stringit(fieldNamesUse)
  837.     PRINT, '      Field Locs  :  ' + ra_stringit(fieldLocationsUse)
  838.     PRINT, '      Field Groups:  ' + ra_stringit(fieldGroupsUse)
  839.     PRINT, '  Template Version:  ' + STRTRIM(STRING(versionUse), 2)
  840.     PRINT
  841.   endif
  842.  
  843.   ;  Try to read the file.
  844.   ;
  845.   pData = ra_read_from_templ(file, templateUse, recordStartUse, $
  846.     numRecordsUse, doVerbose, numFieldsRead, FieldNames, count, header=header)
  847.  
  848.   ;  Return zero if no records read.
  849.   ;
  850.   if (count eq 0) then RETURN, 0
  851.  
  852.   ;  Put the fields into a structure.
  853.   ;
  854.   data = create_struct(strcompress(FieldNames(0),/rem), *pData(0))
  855.   for i=1, numFieldsRead-1 do $
  856.     data = create_struct(data, strcompress(FieldNames(i),/rem), *pData(i))
  857.  
  858.   ;  Clean up the heap data.
  859.   ;
  860.   for i = 0, numFieldsRead-1 do $
  861.     PTR_FREE, pData(i)
  862.  
  863.   ;  Print verbose information.
  864.   ;
  865.   if (doVerbose) then begin
  866.     PRINT, 'Output data ...'
  867.     HELP, data, /STRUCTURES
  868.     PRINT
  869.   endif
  870.  
  871.   ;  Return the structure.
  872.   ;
  873.   RETURN, data
  874.  
  875. end            ; read_ascii
  876.  
  877. ; -----------------------------------------------------------------------------
  878.  
  879.